// 
// Entify project
//  
// Author:
//       Lauri Taimila <lauri@taimila.com>
// 
// Copyright (c) 2010 Lauri Taimila
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using Cairo;
using System.Collections.Generic;
using MonoDevelop.Core;

namespace MonoDevelop.EntifyEditor
{
	/// <summary>
	/// Implements drawing methods for entity set canvas widget.
	/// </summary>
	public static class EntityCanvasDrawer
	{

		/// <summary>
		/// Draws grid to canvas. Grid consist of white background
		/// and gray horizontal & vertical lines.
		/// </summary>
		public static void DrawGrid(Context g, int width, int height, int spacing)
		{
			g.Save();

			g.Color = new Color(1, 1, 1); // White

			g.MoveTo(0,0);
			g.LineTo(width, 0);
			g.LineTo(width, height);
			g.LineTo(0, height);
			g.ClosePath();
			g.Fill();

			g.LineWidth = 1;
			g.Color = new Color(0.95, 0.95, 0.95); // Light gray

			// Draw vertical lines.
			for(int i = 0; i <= width ; i = i + spacing)
			{
				g.MoveTo(i + 0.5, 0.5);
				g.LineTo(i + 0.5, height + 0.5);
				g.Stroke();
			}

			// Draw horizontal lines.
			for(int i = 0; i <= height ; i = i + spacing)
			{
				g.MoveTo(0, i + 0.5);
				g.LineTo(width + 0.5, i + 0.5);
				g.Stroke();
			}

			g.Restore();
		}

		/// <summary>
		/// Draw entity to canvas. Entity contains position where it should be drawn.
		/// </summary>
		/// <param name="g">
		/// Context to draw on. <see cref="Context"/>
		/// </param>
		/// <param name="e">
		/// Entity to draw. <see cref="Entity"/>
		/// </param>
		/// <param name="active">
		/// Should this entity be drawn as active entity. <see cref="System.Boolean"/>
		/// </param>
		public static void DrawEntity(Context g, Entity e, bool active)
		{
			EntityCanvasDrawer.DrawEntityContainer(g, e, active);
			EntityCanvasDrawer.DrawEntityTitle(g, e);
			EntityCanvasDrawer.DrawEntityProperties(g, e);
		}

		/// <summary>
		/// Draw entity relation as link between two entity containers.
		/// </summary>
		public static void DrawRelation(Context g, RelationInformation relationInfo)
		{
			Entity source = relationInfo.Source;
			Entity destination = relationInfo.Destination;

			// Draw relation only when destination type has been set
			if(destination == null)
			{
				return;
			}

			if(source == destination)
			{
				EntityCanvasDrawer.DrawSelfRelation(g, source, relationInfo.ToMany);
			}
			else
			{
				List<PointD> points = EntityCanvasDrawer.GetRelationPoints(source, destination);

				// Draw line between entities
				g.Save();
				g.LineWidth = 2;
				g.Antialias = Antialias.Subpixel;
				g.Color = source.GetColor(0);
				g.MoveTo(points[0]);
				g.LineTo(points[1]);
				g.Stroke();
				g.Restore();

				// Draw larger circle
				if(relationInfo.ToMany)
				{
					g.Save();
					g.LineWidth = 1;
					g.Color = source.GetColor(0.35);
					g.Arc(points[0].X, points[0].Y , 7.5 , 0.0, 2.0 * Math.PI);
					g.FillPreserve();
					g.Color = source.GetColor(-0.35);
					g.Stroke();
					g.Restore();
				}

				// Draw source connector (circle)
				g.Save();
				g.LineWidth = 1;
				g.Color = source.GetColor(0.35);
				g.Arc(points[0].X, points[0].Y , 3.5 , 0.0, 2.0 * Math.PI);
				g.FillPreserve();
				g.Color = source.GetColor(-0.35);
				g.Stroke();
				g.Restore();

				// Draw destination connector (circle)
				g.Save();
				g.LineWidth = 1;
				g.Color = destination.GetColor(0.35);
				g.Arc(points[1].X, points[1].Y , 3.5 , 0.0, 2.0 * Math.PI);
				g.FillPreserve();
				g.Color = destination.GetColor(-0.35);
				g.Stroke();
				g.Restore();
			}
		}

		/// <summary>
		/// Gets coordinates for relation line. Coordinates are returned as
		/// Point list where first point is source and second destination.
		/// </summary>
		private static List<PointD> GetRelationPoints(Entity source, Entity destination)
		{
			bool sourceOnTop = false;
			bool sourceOnBottom = false;
			bool sameY = false;
			bool sourceOnLeft = false;
			bool sourceOnRight = false;
			bool sameX = false;

			PointD sourcePoint = new PointD(0,0);
			PointD destinationPoint = new PointD(0,0);

			if(source.Point.Y + source.Size.Y < destination.Point.Y)
			{
				sourceOnTop = true;
			}
			else if(source.Point.Y > destination.Point.Y + destination.Size.Y)
			{
				sourceOnBottom = true;
			}
			else
			{
				sameY = true;
			}

			if(source.Point.X + source.Size.X < destination.Point.X)
			{
				sourceOnLeft = true;
			}
			else if(source.Point.X > destination.Point.X + destination.Size.X)
			{
				sourceOnRight = true;
			}
			else
			{
				sameX = true;
			}

			if(sourceOnLeft)
			{
				destinationPoint = new PointD(destination.Point.X, destination.Point.Y + (destination.Size.Y / 2));
			}
			else if(sourceOnRight)
			{
				destinationPoint = new PointD(destination.Point.X + destination.Size.X, destination.Point.Y + (destination.Size.Y / 2));
			}

			if(sourceOnTop)
			{
				sourcePoint = new PointD(source.Point.X + (source.Size.X / 2), source.Point.Y + source.Size.Y);
			}
			else if(sourceOnBottom)
			{
				sourcePoint = new PointD(source.Point.X + (source.Size.X / 2), source.Point.Y);
			}

			if(sameX && sourceOnTop)
			{
				sourcePoint = new PointD(source.Point.X + (source.Size.X / 2), source.Point.Y + source.Size.Y);
				destinationPoint = new PointD(destination.Point.X + (destination.Size.X / 2), destination.Point.Y);
			}
			else if(sameX && sourceOnBottom)
			{
				sourcePoint = new PointD(source.Point.X + (source.Size.X / 2), source.Point.Y);
				destinationPoint = new PointD(destination.Point.X + (destination.Size.X / 2), destination.Point.Y + destination.Size.Y);
			}

			if(sameY && sourceOnLeft)
			{
				sourcePoint = new PointD(source.Point.X + source.Size.X, source.Point.Y + (source.Size.Y / 2));
				destinationPoint = new PointD(destination.Point.X, destination.Point.Y + (destination.Size.Y / 2));
			}
			else if(sameY && sourceOnRight)
			{
				sourcePoint =  new PointD(source.Point.X, source.Point.Y + (source.Size.Y / 2));
				destinationPoint = new PointD(destination.Point.X + destination.Size.X, destination.Point.Y + (destination.Size.Y / 2));
			}

			return new List<PointD>() { sourcePoint, destinationPoint };
		}

		/// <summary>
		/// Draw self relation to specified entity.
		/// </summary>
		private static void DrawSelfRelation (Context g, Entity entity, bool ToMany)
		{
			int loopSize = 18;

			g.Save();
			g.LineWidth = 2;
			g.Color = entity.GetColor(0);
			g.Arc(entity.Point.X, entity.Point.Y, loopSize, 1.57079633, 6.28318531);
			g.Stroke();
			g.Restore();

			// Draw larger circle
			if(ToMany)
			{
				g.Save();
				g.LineWidth = 1;
				g.Color = entity.GetColor(0.35);
				g.Arc(entity.Point.X  + loopSize, entity.Point.Y, 7.5 , 0.0, 2.0 * Math.PI);
				g.FillPreserve();
				g.Color = entity.GetColor(-0.35);
				g.Stroke();
				g.Restore();
			}

			// Draw destination circle
			g.Save();
			g.LineWidth = 1;
			g.Color = entity.GetColor(0.35);
			g.Arc(entity.Point.X  + loopSize, entity.Point.Y, 3.5 , 0.0, 2.0 * Math.PI);
			g.FillPreserve();
			g.Color = entity.GetColor(-0.35);
			g.Stroke();
			g.Restore();

			// Draw source circle
			g.Save();
			g.LineWidth = 1;
			g.Color = entity.GetColor(0.35);
			g.Arc(entity.Point.X, entity.Point.Y + loopSize , 3.5 , 0.0, 2.0 * Math.PI);
			g.FillPreserve();
			g.Color = entity.GetColor(-0.35);
			g.Stroke();
			g.Restore();
		}

		/// <summary>
		/// Draw properties to entity conteiner.
		/// </summary>
		private static void DrawEntityProperties(Context g, Entity e)
		{
			double propertyTextLineHeight = 17;
			double propertyX = e.Point.X + 5;
			double propertyY = e.Point.Y + 40;
			double numberOfPropertiesFit = Math.Ceiling((e.Size.Y - 60) / propertyTextLineHeight);
			double numberOfPropertiesToDraw = 0;

			// Draw 3 dots if space is too small for all properties
			if(numberOfPropertiesFit >= e.Properties.Count)
			{
				numberOfPropertiesToDraw = e.Properties.Count;
			}
			else
			{
				numberOfPropertiesToDraw = numberOfPropertiesFit;
				double dotY = e.Point.Y + e.Size.Y - 10;
				double dotX = e.Point.X + (e.Size.X / 2);
				g.Save();
				g.LineWidth = 1;

				g.Color = e.GetColor(0.6);
				g.Arc(dotX, dotY+1 , 2.5, 0.0, 2.0 * Math.PI);
				g.Fill();
				g.Arc(dotX - 10, dotY+1 , 2.5, 0.0, 2.0 * Math.PI);
				g.Fill();
				g.Arc(dotX + 10, dotY+1 , 2.5, 0.0, 2.0 * Math.PI);
				g.Fill();

				g.Color = e.GetColor(-0.35);
				g.Arc(dotX, dotY , 2.5, 0.0, 2.0 * Math.PI);
				g.Fill();
				g.Arc(dotX - 10, dotY , 2.5, 0.0, 2.0 * Math.PI);
				g.Fill();
				g.Arc(dotX + 10, dotY , 2.5, 0.0, 2.0 * Math.PI);
				g.Fill();

				g.Restore();
			}

			// Draw property names
			g.Save();
				g.Color = e.GetColor(-0.35);
				g.Antialias = Antialias.Subpixel;
				g.SelectFontFace("Sans", FontSlant.Normal, FontWeight.Normal);
				g.SetFontSize(12);
				g.LineWidth = 1;

				for(int i = 0; i < numberOfPropertiesToDraw; i++)
				{
					string text = e.Properties[i].Name;
					bool textTooLong = true;

					// Resize property name so that it fits into entity container
					do
					{
						TextExtents textSize = g.TextExtents(text);
						if(textSize.Width + 10 > e.Size.X)
						{
							text = text.Substring(0, text.Length - 4);
							text = text + "...";
						}
						else
						{
							textTooLong = false;
						}
					}
					while(textTooLong);

					g.MoveTo(propertyX, propertyY);
					g.ShowText(text);
					propertyY = propertyY + propertyTextLineHeight;
				}

			g.Restore();
		}

		/// <summary>
		/// Draw entity title.
		/// </summary>
		private static void DrawEntityTitle (Context g, Entity e)
		{
			// Draw entity title
			g.Save();
				g.Color = e.GetColor(0.6);
				g.SelectFontFace("Sans", FontSlant.Normal, FontWeight.Bold);
				g.SetFontSize(14);
				g.LineWidth = 1;
	
				TextExtents textSize = g.TextExtents(e.Name);
				g.MoveTo(e.Point.X + ((e.Size.X - textSize.Width) / 2) , e.Point.Y + 18);

				g.ShowText(e.Name);
			g.Restore();

			g.Save();
				g.Color = e.GetColor(-0.35);
				g.SelectFontFace("Sans", FontSlant.Normal, FontWeight.Bold);
				g.SetFontSize(14);
				g.LineWidth = 1;

				textSize = g.TextExtents(e.Name);
				g.MoveTo(e.Point.X + ((e.Size.X - textSize.Width) / 2) , e.Point.Y + 17);
	
				g.ShowText(e.Name);
			g.Restore();
		}

		/// <summary>
		/// Draws the container box for entity.
		/// </summary>
		private static void DrawEntityContainer(Context g, Entity e, bool active)
		{
			g.Save();
			g.Color = e.GetColor(0.6);
			g.SelectFontFace("Sans", FontSlant.Normal, FontWeight.Bold);
			g.SetFontSize(14);
			g.LineWidth = 1;
			TextExtents textSize = g.TextExtents(e.Name);
			g.Restore();

			// Force enity width so that entity name fits into the box
			if(e.Size.X <= textSize.Width + 14)
			{
				e.Size = new PointD(textSize.Width + 14, e.Size.Y);
			}

			// Draw box
			g.Save();
			Cairo.Gradient gradientPattern = new Cairo.LinearGradient(e.Point.X, e.Point.Y, e.Point.X, e.Point.Y + (e.Size.Y * 2));
			gradientPattern.AddColorStop(0,e.GetColor(0.35));
			gradientPattern.AddColorStop(1, e.GetColor(0));

			g.Pattern = gradientPattern;
			EntityCanvasDrawer.DrawRoundedRectangle(g, e.Point.X, e.Point.Y, e.Size.X, e.Size.Y, 7);
			g.FillPreserve();
			g.Color = e.GetColor(-0.35);

			if(active)
			{
				g.LineWidth = 3;
			}
			else
			{
				g.LineWidth = 1;
			}
			g.Stroke();
			g.Restore();

			// Draw separator line between entity name and properties
			g.Save();
				g.Color = e.GetColor(-0.35);
				g.LineWidth = 1;
				g.MoveTo(e.Point.X, e.Point.Y + 25);
				g.LineTo(e.Point.X + e.Size.X, e.Point.Y + 25);
				g.Stroke();
			g.Restore();

			// Draw resize hint to entity container if it's active
			if(active)
			{
				g.Save();
					g.Color = e.GetColor(-0.1);
					g.LineWidth = 1;
					g.MoveTo(e.Point.X + e.Size.X - 20, e.Point.Y + e.Size.Y - 4);
					g.LineTo(e.Point.X + e.Size.X - 4, e.Point.Y + e.Size.Y - 20);
					g.MoveTo(e.Point.X + e.Size.X - 16, e.Point.Y + e.Size.Y - 4);
					g.LineTo(e.Point.X + e.Size.X - 4, e.Point.Y + e.Size.Y - 16);
					g.MoveTo(e.Point.X + e.Size.X - 12, e.Point.Y + e.Size.Y - 4);
					g.LineTo(e.Point.X + e.Size.X - 4, e.Point.Y + e.Size.Y - 12);
					g.MoveTo(e.Point.X + e.Size.X - 8, e.Point.Y + e.Size.Y - 4);
					g.LineTo(e.Point.X + e.Size.X - 4, e.Point.Y + e.Size.Y - 8);
					g.Stroke();
				g.Restore();
			}
		}

		/// <summary>
		/// Creates path for rounded rectangle.
		/// </summary>
		private static void DrawRoundedRectangle (Cairo.Context gr, double x, double y, double width, double height, double radius)
	    {
			gr.Save ();
				if ((radius > height / 2) || (radius > width / 2))
				{
				    radius = Math.Min(height / 2, width / 2);
				}

				gr.MoveTo (x, y + radius);
				gr.Arc (x + radius, y + radius, radius, Math.PI, -Math.PI / 2);
				gr.LineTo (x + width - radius, y);
				gr.Arc (x + width - radius, y + radius, radius, -Math.PI / 2, 0);
				gr.LineTo (x + width, y + height - radius);
				gr.Arc (x + width - radius, y + height - radius, radius, 0, Math.PI / 2);
				gr.LineTo (x + radius, y + height);
				gr.Arc (x + radius, y + height - radius, radius, Math.PI / 2, Math.PI);
				gr.ClosePath ();
			gr.Restore ();
	    }
	}
}
